#!/usr/bin/python
# -*- coding: UTF-8 -*-

import sys
import os
import numpy as np

def sigmoid(z):
    """
    参数:
    z -- 一个数值或者一个numpy数组.
    返回值:
    s -- 经过sigmoid算法计算后的值，在[0,1]范围内
    """
    #对sigmoid函数的优化，避免了出现极大的数据溢出
    # s = 1.0/(1+np.exp(-z))
    s = np.full((1, z.shape[1]), 0.0)
    # print("z.shape[1]=", z.shape[1], "s.shape", s.shape)
    for i in range(z.shape[1]):
        t = z[0][i]
        if t >= 0:
            s[0][i] = 1.0/(1 + np.exp(-t))
        else:
            s[0][i] = np.exp(t)/(1 + np.exp(t))
    return s


def initialize_with_zeros(dim):
    """
    这个函数用于初始化权重数组w和偏置/阈值b.
    
    参数:
    dim -- w的大小，看上面的神经网络模型图可知，dim在本例中是12288，因为一个特征输入对应一个权重。
    
    返回值:
    w -- 权重数组
    b -- 偏置bias
    """

    w = np.zeros((dim,1))
    b = 0
    
    return w, b

def propagate(w, b, X, Y):
    """
    参数:
    w -- 权重数组，维度是(12288, 1)
    b -- 偏置bias
    X -- 图片的特征数据，维度是 (12288, 209)
    Y -- 图片对应的标签，0或1，0是无猫，1是有猫，维度是(1,209)

    返回值:
    cost -- 成本
    dw -- w的梯度
    db -- b的梯度
    """
    
    m = X.shape[1]
    
    # 前向传播
    A = sigmoid(np.dot(w.T, X) + b)                             
    cost = -np.sum(Y*np.log(A + 1e-5) + (1-Y)*np.log(1-A + 1e-5)) / m  
    
    # 反向传播
    dZ = A - Y
    dw = np.dot(X,dZ.T) / m
    db = np.sum(dZ) / m
    
    # 将dw和db保存到字典里面
    grads = {"dw": dw,
             "db": db}
    
    return grads, cost

def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
    """    
    参数:
    w -- 权重数组，维度是 (12288, 1)
    b -- 偏置bias
    X -- 图片的特征数据，维度是 (12288, 209)
    Y -- 图片对应的标签，0或1，0是无猫，1是有猫，维度是(1,209)
    num_iterations -- 指定要优化多少次
    learning_rate -- 学习步进，是我们用来控制优化步进的参数
    print_cost -- 为True时，每优化100次就把成本cost打印出来,以便我们观察成本的变化
    
    返回值:
    params -- 优化后的w和b
    costs -- 每优化100次，将成本记录下来，成本越小，表示参数越优化
    """
    
    costs = []
    
    for i in range(num_iterations):        
        grads, cost = propagate(w, b, X, Y) # 计算得出梯度和成本
                
        # 从字典中取出梯度
        dw = grads["dw"]
        db = grads["db"]
        
        # 进行梯度下降，更新参数，使其越来越优化，使成本越来越小
        w = w - learning_rate * dw
        b = b - learning_rate * db
        
        # 将成本记录下来
        if i % 100 == 0:
            costs.append(cost)
            if print_cost:
                print ("优化%i次后成本是: %f" %(i, cost))
    
    params = {"w": w,
              "b": b}
    return params, costs

def predict(w, b, X):
    '''    
    参数:
    w -- 权重数组，维度是 (12288, 1)
    b -- 偏置bias
    X -- 图片的特征数据，维度是 (12288, 图片张数)
    
    返回值:
    Y_prediction -- 对每张图片的预测结果
    '''    
    m = X.shape[1]
    Y_prediction = np.zeros((1,m))
       
    tt = np.dot(w.T, X) + b
    A = sigmoid(tt)  # 通过这行代码来对图片进行预测
    
    # 上面得出的预测结果是小数的形式，为了方便后面显示，我们将其转换成0和1的形式（大于等于0.5就是1/有猫，小于0.5就是0/无猫）
    for i in range(A.shape[1]):
        if A[0,i] >= 0.5:
            Y_prediction[0,i] = 1
    
    return Y_prediction


def initialize_parameters(n_x, n_h, n_y):
    """
    参数：
    n_x--输入层神经元个数
    n_h--隐藏层神经元个数
    n_y--输出层神经元个数
    """
    np.random.seed(2)
    w1 = np.random.randn(n_h, n_x)*0.01
    b1 = np.zeros((n_h, 1))
    w2 = np.random.randn(n_y, n_h)*0.01
    b2 =np.zeros((n_y, 1))
    parameters = {
        "W1":w1,
        "b1":b1,
        "W2":w2,
        "b2":b2}

    return parameters

def forward_propagation(X, parameters):
    """
    参数:
    X -- 输入特征，维度是 (横纵坐标, 样本数)
    parameters -- 参数w和b
    
    Returns:
    A2 -- The sigmoid output of the second activation
    cache -- a dictionary containing "Z1", "A1", "Z2" and "A2"
    """
    
    m = X.shape[1] # 获取样本数
    print("样本数：" + str(m))
    
    # 从字典中取出参数
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    # 实现前向传播算法
    Z1 = np.dot(W1, X) + b1
    A1 = np.tanh(Z1) # 第一层的激活函数我们使用tanh。numpy库里面已经帮我们实现了tanh工具函数
    Z2 = np.dot(W2, A1) + b2
    A2 = sigmoid(Z2) # 第二层我们使用sigmoid，因为我们要解决的这个问题属于二分问题。这个函数是我们自己在planar_utils里面实现的。

    # 将这些前向传播时得出的值保存起来，因为在后面进行反向传播计算时会用到他们。
    cache = {"Z1": Z1,
             "A1": A1,
             "Z2": Z2,
             "A2": A2}
    
    return A2, cache